﻿using System;
using System.Management.Automation;
using System.Management.Automation.Provider;

namespace PSPROOFUtils
{
    /// <summary>
    /// Provider that implements datasets as a list of datasets...
    /// </summary>
    [CmdletProvider("PROOFDS", ProviderCapabilities.None)]
    public class PROOFDatasetProvider : ContainerCmdletProvider, IContentCmdletProvider
    {
        #region Drive Overrides
        /// <summary>
        /// Create a new powershell drive for our proof server.
        /// </summary>
        /// <param name="drive"></param>
        /// <returns></returns>
        protected override PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            var connection = ROOTNET.NTProof.Open(drive.Root, "masteronly");
            if (connection == null || !connection.IsValid())
                throw new ArgumentException(string.Format("Unable to connect to PROOF server at '{0}'", drive.Root));
            var b = new ProofDrive(drive, connection);
            return b;
        }

        /// <summary>
        /// Helper function to save us typing - get at the drive object, but
        /// marked as our own type so we don't have to write the conversion over and over.
        /// </summary>
        private ProofDrive PROOFDrive
        {
            get { return this.PSDriveInfo as ProofDrive; }
        }


        /// <summary>
        /// Turn off the drive - close the connection to PROOF.
        /// </summary>
        /// <param name="drive"></param>
        /// <returns></returns>
        protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
        {
            var p = drive as ProofDrive;
            if (p == null)
                throw new InvalidOperationException("Attempt to remove a PROOF drive for a drive that isn't PROOF!");
            p.Close();

            return drive;
        }

        #endregion

        #region Item Overrides
        /// <summary>
        /// Return an item from our drive - we just get the item object here!
        /// </summary>
        /// <param name="path"></param>
        protected override void GetItem(string path)
        {
            var i = PROOFDrive.GetDSItem(path);
            WriteItemObject(i, i.Name, false);
        }

        /// <summary>
        /// Set an dataset to a value.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        protected override void SetItem(string path, object value)
        {
            if (!ItemExists(path))
                throw new ArgumentException(string.Format("Dataset '{0}' does not exist", path));

            if (value == null)
                throw new ArgumentException(string.Format("Cannot set datest '{0}''s file list to be null", path));

            var ds = PROOFDrive.GetDSItem(path, fullInformation: true);
            ds.AddItem(value);

            var opt = DynamicParameters as DSWritingParameters;

            ds.UpdateServer(opt.Verify);
        }

        /// <summary>
        /// Get back parameters for the command line.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        protected override object SetItemDynamicParameters(string path, object value)
        {
            return new DSWritingParameters();
        }

        /// <summary>
        /// Check to see if the item exists in our current proof guy.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        protected override bool ItemExists(string path)
        {
            return PROOFDrive.HasDataset(path);
        }

        protected override void ClearItem(string path)
        {
            base.ClearItem(path);
        }

        protected override void InvokeDefaultAction(string path)
        {
            base.InvokeDefaultAction(path);
        }

        protected override string[] ExpandPath(string path)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Check to see if the path is valid or not.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        protected override bool IsValidPath(string path)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Contain an extra parameters to be used for the -GetItem guy
        /// </summary>
        public class GetItemExtraParameters
        { }

        #endregion

        #region Container Overrides

        protected override void CopyItem(string path, string copyPath, bool recurse)
        {
            base.CopyItem(path, copyPath, recurse);
        }

        /// <summary>
        /// Return a list of child items in the store.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="recurse"></param>
        /// <remarks>
        /// PROOF has no child items below the top level, so if there is anything below we can just return.
        /// </remarks>
        protected override void GetChildItems(string path, bool recurse)
        {
            //
            // Only top level for us!
            //

            if (path != "")
                return;

            //
            // Get a list of the dataset names
            //

            foreach (var item in PROOFDrive.GetDSItems())
            {
                WriteItemObject(item, item.Name, false);
            }
        }

        /// <summary>
        /// Return a list of names for the child items.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="returnContainers"></param>
        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            if (path == "")
                return;

            foreach (var item in PROOFDrive.GetDSItems())
            {
                WriteItemObject(item.Name, path, false);
            }
        }

        /// <summary>
        /// Create a new item. It will be empty, of course. User must do Set-Content in order to get that to work!
        /// </summary>
        /// <param name="path"></param>
        /// <param name="itemTypeName"></param>
        /// <param name="newItemValue"></param>
        protected override void NewItem(string path, string itemTypeName, object newItemValue)
        {
            //
            // Checks
            //

            if (itemTypeName != null)
                throw new InvalidOperationException("PROOFDS can only create an PROOF Dataset type - do not specify a type name");

            if (ItemExists(path))
                throw new ArgumentException(string.Format("The PROOF dataset '{0}' already exists", path));

            //
            // Create the item.
            //

            var ds = PROOFDrive.CreateNewDS(path);

            //
            // If the ds has a value, then we can upload a file to it...
            //

            if (newItemValue != null)
            {
                ds.AddItem(newItemValue);

                //
                // Update the server... otherwise nothing happens until the dataset has some files in it!
                //

                var opt = DynamicParameters as DSWritingParameters;
                ds.UpdateServer(opt.Verify);
            }

            //
            // Send the dataset on the pipeline.
            //

            WriteItemObject(ds, path, false);
        }

        /// <summary>
        /// We add a few possible parameters to the new-item cmdlet.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="itemTypeName"></param>
        /// <param name="newItemValue"></param>
        /// <returns></returns>
        protected override object NewItemDynamicParameters(string path, string itemTypeName, object newItemValue)
        {
            return new DSWritingParameters();
        }

        /// <summary>
        /// Remove a dataset from the PROOF server and our local cache.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="recurse"></param>
        protected override void RemoveItem(string path, bool recurse)
        {
            if (!ItemExists(path))
                throw new ArgumentException(string.Format("Can't remove dataset '{0}' from proof server because it doesn't exist!", path));

            PROOFDrive.RemoveDS(path);
        }

        /// <summary>
        /// See if there are any child items. Basically, return this as true if it is top level. Otherwise... NOT.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        protected override bool HasChildItems(string path)
        {
            return path == "";
        }

        protected override bool ConvertPath(string path, string filter, ref string updatedPath, ref string updatedFilter)
        {
            return base.ConvertPath(path, filter, ref updatedPath, ref updatedFilter);
        }

        #endregion

        #region Content Methods

        /// <summary>
        /// This doesn't really make sense, so we will do our best to emulate it.
        /// </summary>
        /// <param name="path"></param>
        public void ClearContent(string path)
        {
            if (!ItemExists(path))
                throw new ArgumentException(string.Format("Dataset '{0}' does not exist", path));

            var ds = PROOFDrive.GetDSItem(path, true);
            ds.ClearAllFiles();
        }

        public object ClearContentDynamicParameters(string path)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns a reader that gets the names of all the files
        /// that are in this dataset.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public IContentReader GetContentReader(string path)
        {
            var item = PROOFDrive.GetDSItem(path, fullInformation: true);
            return new DSContentReader(item);
        }

        /// <summary>
        /// We need no special parameters here.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public object GetContentReaderDynamicParameters(string path)
        {
            return null;
        }

        /// <summary>
        /// Get a content writer - that will allow us to write items to the dataset provider.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public IContentWriter GetContentWriter(string path)
        {
            if (!ItemExists(path))
                throw new ArgumentException(string.Format("Dataset '{0}' does not exist", path));

            var options = DynamicParameters as DSWritingParameters;

            var ds = PROOFDrive.GetDSItem(path);
            return new DSContentWriter(ds, options.Verify);
        }

        /// <summary>
        /// Additional command line parameters when we set the content
        /// </summary>
        class DSWritingParameters
        {
            /// <summary>
            /// Get/Set Verify - should we verify the dataset when we register it and update it?
            /// </summary>
            [Parameter(HelpMessage = "Causes the PROOF server to verify the dataset when it is registered or updated.")]
            public SwitchParameter Verify { get; set; }

            /// <summary>
            /// Initialize with the defaults.
            /// </summary>
            public DSWritingParameters()
            {
                Verify = false;
            }
        }

        /// <summary>
        /// Return parameters for writing some content.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public object GetContentWriterDynamicParameters(string path)
        {
            return new DSWritingParameters();
        }
        #endregion
    }
}
